// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#if __ANDROID_API__ >= 24
#include <android/choreographer.h>
#include <android/looper.h>
#endif

#include "CasDecodeController.h"
#include "CasVideoUtil.h"

namespace {
    const int WAITING_TIME_US = 10000;
}

CasDecodeController *CasDecodeController::g_instance = nullptr;

#if __ANDROID_API__ >= 24
AChoreographer *choreographer;
CasDecodeController *g_controller = nullptr;

uint32_t CasDecodeController::ReduceFps(uint64_t frameBuffer)
{
    const uint64_t maxBuffCount15 = 12;
    const uint64_t maxBuffCount30 = 8;
    const uint64_t maxBuffCount48 = 4;
    uint32_t updateIdx = FPSPATTERNS60;
    if (frameBuffer >= maxBuffCount15) {
        updateIdx = FPSPATTERNS15;
    } else if (frameBuffer >= maxBuffCount30) {
        updateIdx = FPSPATTERNS30;
    } else if (frameBuffer >= maxBuffCount48) {
        updateIdx = FPSPATTERNS48;
    }
    return updateIdx;
}

bool CasDecodeController::IsDiscardFrame(uint32_t updatePatternIdx)
{
    if (updatePatternIdx >= FPSPATTERNS_MAX) {
        INFO("IsDiscardFrame index over limit, index = %u", updatePatternIdx);
        updatePatternIdx = FPSPATTERNS60;
    }
    // 丢帧策略变化
    if (updatePatternIdx != m_UpdatePatternIdx) {
        ClearDiscardFrameCache();
        m_UpdatePatternIdx = updatePatternIdx;
    }
    if (m_HoldFrames > 1) {
        m_HoldFrames--;
    } else {
        m_UpdatePatternOffset = (m_UpdatePatternOffset + 1) % strlen(UPDATE_FPSPATTERNS[m_UpdatePatternIdx]);
        char ch = UPDATE_FPSPATTERNS[m_UpdatePatternIdx][m_UpdatePatternOffset];
        m_HoldFrames = ch - '0';
    }
    return !(m_HoldFrames == 1);
}

void CasDecodeController::ClearDiscardFrameCache()
{
    m_UpdatePatternIdx = FPSPATTERNS60;
    m_UpdatePatternOffset = 0;
    m_HoldFrames = 0;
}

void FrameCallback(long frameTime, void* data)
{
    if (!g_controller->IsStatus(EngineStat::ENGINE_RUNNING)) {
        return;
    }

    bool isDecodeFirstFrame = false;
    int iRet = 0;
    uint64_t startUs = 0;
    uint64_t nowUs = 0;

    CasVideoUtil *casVideoUtil = CasVideoUtil::GetInstance();
    if (casVideoUtil != nullptr) {
        startUs = casVideoUtil->GetNow();
    }

    CasDecoder* decoder = g_controller->GetCasDecoder();
    CasFirstVideoFrameListener* firstVideoFrameListener = g_controller->GetCasFirstVideoFrameListener();
    CasVideoDecodeStatListener* videoDecodeStatListener = g_controller->GetCasVideoDecodeStatListener();

    // 支持网络缓存堆积时按照策略丢帧
    bool isDiscard = true;
    const uint64_t discardBuffCount = 4;
    uint64_t frameBuffer = 0;
    if (g_controller->m_InputFrameNum > g_controller->m_DisplayFrameNum) {
        frameBuffer = g_controller->m_InputFrameNum - g_controller->m_DisplayFrameNum;
    }
    // 缓存堆积时，根据策略丢缓存后马上出下一帧，一次处理2帧
    uint64_t reduceBegin = 0;
    uint64_t reduceEnd = 0;
    uint64_t display1Begin = 0;
    uint64_t display1End = 0;
    if (frameBuffer >= discardBuffCount) {
        reduceBegin = casVideoUtil->GetNow();
        uint32_t updatePatternIdx = g_controller->ReduceFps(frameBuffer);
        if (g_controller->isStartDiscard == false && g_controller->m_UpdatePatternIdx != updatePatternIdx
            && g_controller->m_UpdatePatternIdx != FPSPATTERNS60) {
            g_controller->isStartDiscard = true;
        }
        isDiscard = g_controller->IsDiscardFrame(updatePatternIdx);
        // 是否丢帧
        if (isDiscard) {
            g_controller->m_DiscardFrameNum++;
            CasVideoUtil::GetInstance()->DropOneFrame();
            INFO("FrameCallback start discard, frameBuffer=%lu, index=%u, last index=%lu, m_DiscardFrameNum=%lu, m_InputFrameNum=%lu, m_DisplayFrameNum=%lu",
                 frameBuffer, updatePatternIdx, g_controller->m_UpdatePatternIdx, g_controller->m_DiscardFrameNum, g_controller->m_InputFrameNum, g_controller->m_DisplayFrameNum);
        }
        reduceEnd = casVideoUtil->GetNow();
        display1Begin = casVideoUtil->GetNow();
        iRet = static_cast<int>(decoder->OutputAndDisplay(!isDiscard));
        display1End = casVideoUtil->GetNow();
        if (iRet != DECODER_OUTPUT_RETRY) {
            g_controller->m_DisplayFrameNum++;
        } else if (iRet == DECODER_OUTPUT_ERR) {
            ERR("FrameCallback OutputAndDisplay error, ret = %d, isDiscard = %d", iRet, isDiscard);
        }
    } else {
        if (g_controller->isStartDiscard == true) {
            g_controller->isStartDiscard = false;
            g_controller->ClearDiscardFrameCache();
        }
    }
    uint64_t display2Begin = casVideoUtil->GetNow();
    if (isDiscard) {
        iRet = static_cast<int>(decoder->OutputAndDisplay(true));
        if (iRet != DECODER_OUTPUT_RETRY) {
            g_controller->m_DisplayFrameNum++;
        }
    }
    uint64_t display2End = casVideoUtil->GetNow();

    if (iRet == DECODER_SUCCESS) {
        if (!isDecodeFirstFrame) {
            isDecodeFirstFrame = true;
            if (firstVideoFrameListener != nullptr) {
                firstVideoFrameListener->OnFirstFrame();
            }
        }
        if (videoDecodeStatListener != nullptr) {
            if (casVideoUtil != nullptr) {
                nowUs = casVideoUtil->GetNow();
            }
            uint64_t latency = (nowUs - startUs) / 1000;
            casVideoUtil->SetTimestamp(nowUs);
            videoDecodeStatListener->OnDecodeOneFrame(latency);
        }
    } else if (iRet == DECODER_OUTPUT_ERR) {
        ERR("FrameCallback OutputAndDisplay error, ret=%d", iRet);
    }
    if (g_controller->m_InputFrameNum % 500 == 1) {
        INFO("FrameCallback m_DisplayFrameNum=%lu, m_InputFrameNum=%lu, m_DiscardFrameNum=%lu",
             g_controller->m_DisplayFrameNum, g_controller->m_InputFrameNum, g_controller->m_DiscardFrameNum);
    }
    uint64_t vsyncEnd = casVideoUtil->GetNow();
    uint64_t duration = vsyncEnd - startUs;
    uint64_t reduceTime = reduceEnd - reduceBegin;
    uint64_t display1Time = display1End - display1Begin;
    uint64_t display2Time = display2End - display2Begin;
    if (duration > 15000) {
        ERR("FrameCallback cost time duration:%lu, reduceTime:%lu, displayTime1:%lu, displayTime2:%lu",
            duration, reduceTime, display1Time, display2Time);
    }

    AChoreographer_postFrameCallback(choreographer, FrameCallback, nullptr);
}
#endif

/*
 * @fn CasDecodeController
 * @brief constructor
 */
CasDecodeController::CasDecodeController() = default;

/*
 * @fn CasDecodeController
 * @brief destructor
 */
CasDecodeController::~CasDecodeController()
{
    this->Destroy();
}

/*
 * @fn GetInstance
 * @brief to get CasDecodeController singleton
 */
CasDecodeController *CasDecodeController::GetInstance()
{
    if (g_instance == nullptr) {
        g_instance = new (std::nothrow) CasDecodeController();
        if (g_instance == nullptr) {
            ERR("Failed to instantiate.");
            return nullptr;
        }
    }
    return g_instance;
}

/*
 * @fn DestroyInstance
 * @brief to release CasDecodeController singleton
 */
uint32_t CasDecodeController::DestroyInstance()
{
    if (g_instance != nullptr) {
        g_instance->Destroy();
        delete g_instance;
        g_instance = nullptr;
        INFO("DestroyInstance success.");
        return SUCCESS;
    }
    INFO("Instance already destroyed.");
    return VIDEO_ENGINE_CLIENT_DESTROY_ERR;
}

CasDecoder* CasDecodeController::GetCasDecoder()
{
    return m_decoder;
}

CasFirstVideoFrameListener* CasDecodeController::GetCasFirstVideoFrameListener()
{
    return m_firstFrameListener;
}

CasVideoDecodeStatListener* CasDecodeController::GetCasVideoDecodeStatListener()
{
    return m_decodeStatListener;
}

/*
 * @fn OutputTaskEntry
 * @param[in] dCtr Instance of (type <tt>CasDecodeController *</tt>)
 * @brief act as a thread entry to output processed data and to display
 */
void OutputTaskEntry(CasDecodeController *controller)
{
    if (controller == nullptr) {
        return;
    }

#if __ANDROID_API__ >= 24
    g_controller = controller;
    ALooper* looper = ALooper_forThread();
    if (looper == nullptr) {
        ERR("Failed to get looper, preparing new one");
        looper = ALooper_prepare(0);
    }
    g_controller->SetSubThreadStatus(true);
    choreographer = AChoreographer_getInstance();
    AChoreographer_postFrameCallback(choreographer, FrameCallback, nullptr);

    while (controller->IsStatus(EngineStat::ENGINE_RUNNING)) {
        int id;
        int events;
        void* data;
        while ((id = ALooper_pollAll(0, nullptr, &events, &data)) >= 0) {
            if (!controller->IsStatus(EngineStat::ENGINE_RUNNING)) {
                controller->SetSubThreadStatus(false);
                return;
            }
        }
    }
    controller->SetSubThreadStatus(false);
#else
    CasVideoUtil *casVideoUtil = CasVideoUtil::GetInstance();
    bool isDecodeFirstFrame = false;
    int iRet = 0;
    uint64_t startUs = 0;
    uint64_t nowUs = 0;
    controller->SetSubThreadStatus(true);
    while (controller->IsStatus(EngineStat::ENGINE_RUNNING)) {
        if (casVideoUtil != nullptr) {
            startUs = casVideoUtil->GetNow();
        }
        iRet = static_cast<int>(controller->m_decoder->OutputAndDisplay());
        if (iRet == DECODER_OUTPUT_ERR) {
            ERR("Sub-Thread exited.");
            break;
        } else if (iRet == DECODER_SUCCESS) {
            if (!isDecodeFirstFrame) {
                isDecodeFirstFrame = true;
                if (controller->m_firstFrameListener != nullptr) {
                    controller->m_firstFrameListener->OnFirstFrame();
                }
            }
            if (casVideoUtil != nullptr) {
                nowUs = casVideoUtil->GetNow();
            }
            if (controller->m_decodeStatListener != nullptr) {
                uint64_t latency = (nowUs - startUs) / 1000;
                controller->m_decodeStatListener->OnDecodeOneFrame(latency);
            }
        }
    }
    controller->SetSubThreadStatus(false);
#endif
}

/*
 * @fn Init
 * @brief to initialise CasDecodeController instance
 * @param[in] nativeWindow View to display of (type <tt>ANativeWindow *</tt>)
 * @param[in] type Method to decode, fixed to DECODER_TYPE_HW, of (type <tt>enum DecoderType</tt>)
 * @param[in] rotation degrees
 * @return errno: SUCCESS
 *                VIDEO_ENGINE_CLIENT_INIT_FAIL
 */
uint32_t CasDecodeController::Init(ANativeWindow *nativeWindow, FrameType frameType, int rotationDegrees)
{
    if (!IsStatus(EngineStat::ENGINE_INVALID)) {
        ERR("Destroy needed first.");
        return VIDEO_ENGINE_CLIENT_INIT_FAIL;
    }
    m_decoder = new (std::nothrow) CasDecoder();
    if (m_decoder == nullptr) {
        ERR("Failed to instantiate CasDecoder.");
        return VIDEO_ENGINE_CLIENT_INIT_FAIL;
    }
    uint32_t ret = m_decoder->Init(nativeWindow, frameType, rotationDegrees);
    if (ret != DECODER_SUCCESS) {
        Destroy();
        if (ret == DECODER_SDK_UNSUPPORTED) {
            ERR("Unsupported SDK version.");
            return VIDEO_ENGINE_CLIENT_SDK_UNSUPPORTED;
        }
        ERR("Failed to initialise CasDecoder.");
        return VIDEO_ENGINE_CLIENT_INIT_FAIL;
    }
    SetStatus(EngineStat::ENGINE_INIT);
    m_InputFrameNum = 0;
    INFO("Init success.");
    return SUCCESS;
}

/*
 * @fn Start
 * @brief to start CasDecodeController instance
 * @return errno: SUCCESS
 *                VIDEO_ENGINE_CLIENT_START_ERR
 */
uint32_t CasDecodeController::Start()
{
    if (IsStatus(EngineStat::ENGINE_INVALID)) {
        ERR("Init needed first.");
        return VIDEO_ENGINE_CLIENT_START_ERR;
    }
    if (IsStatus(EngineStat::ENGINE_RUNNING)) {
        ERR("Already started.");
        return VIDEO_ENGINE_CLIENT_START_ERR;
    }
    if (IsStatus(EngineStat::ENGINE_INIT)) {
        if (m_decoder->Start() != DECODER_SUCCESS) {
            ERR("Failed to start CasDecoder.");
            Destroy();
            return VIDEO_ENGINE_CLIENT_START_ERR;
        }
    }
    SetStatus(EngineStat::ENGINE_RUNNING);
    // to start sub-thread for the output of decoded frame
    std::thread outputTask(OutputTaskEntry, this);
    if (outputTask.joinable()) {
        outputTask.detach();
    }

    m_DisplayFrameNum = 0;
    m_DiscardFrameNum = 0;
    isStartDiscard = false;
    ClearDiscardFrameCache();

    INFO("Start success.");
    return SUCCESS;
}

/*
 * @fn Decode
 * @brief CasDecodeController instance places H264 into CasDecoder
 * @param[in] buf Initial address of current frame of (type <tt>const int8_t *</tt>)
 * @param[in] length Current frame length of (type <tt>size_t</tt>)
 * @return errno: SUCCESS
 *                VIDEO_ENGINE_CLIENT_DECODE_ERR
 *                VIDEO_ENGINE_CLIENT_PARAM_INVALID
 */
uint32_t CasDecodeController::Decode(const uint8_t *buf, size_t length)
{
    if (!IsStatus(EngineStat::ENGINE_RUNNING)) {
        ERR("Not running.");
        return VIDEO_ENGINE_CLIENT_DECODE_ERR;
    }
    if (!IsSubThreadRunning()) {
        ERR("Sub-Thread not running.");
        return VIDEO_ENGINE_CLIENT_DECODE_ERR;
    }
    if (m_decoder->Input(buf, length) != DECODER_SUCCESS) {
        ERR("Failed to process input data.");
        return VIDEO_ENGINE_CLIENT_DECODE_ERR;
    }
    m_InputFrameNum++;
    return SUCCESS;
}

/*
 * @fn Stop
 * @brief to stop CasDecodeController instance
 * @return errno: SUCCESS
 *                VIDEO_ENGINE_CLIENT_STOP_ERR
 */
uint32_t CasDecodeController::Stop()
{
    if (IsStatus(EngineStat::ENGINE_RUNNING)) {
        SetStatus(EngineStat::ENGINE_STOP);
        while (m_subThreadRunning) {
            usleep(WAITING_TIME_US);
        }
        INFO("Stop success.");
        return SUCCESS;
    }
    ERR("Invalid Status.");
    return VIDEO_ENGINE_CLIENT_STOP_ERR;
}

/*
 * @fn Destroy
 * @brief to destroy CasDecodeController instance
 */
void CasDecodeController::Destroy() noexcept
{
    if (Stop() == VIDEO_ENGINE_CLIENT_STOP_ERR) {
        INFO("Not Running");
    }
    // to release decoder
    if (m_decoder != nullptr) {
        m_decoder->Destroy();
        delete m_decoder;
        m_decoder = nullptr;
    }
    m_firstFrameListener = nullptr;
    m_decodeStatListener = nullptr;

    SetStatus(EngineStat::ENGINE_INVALID);
    INFO("Destroy success.");
}

/*
 * @fn SetSubThreadStatus
 * @brief to set CasDecodeController sub-thread work status
 * @param[in] status, true: sub-thread running, false: sub-thread stopped
 * @return void
 */
void CasDecodeController::SetSubThreadStatus(bool status)
{
    m_subThreadRunning = status;
}

/*
 * @fn IsSubThreadRunning
 * @brief to check if CasDecodeController sub-thread running
 * @return true: sub-thread running, false: sub-thread stopped
 */
bool CasDecodeController::IsSubThreadRunning() const
{
    return m_subThreadRunning;
}

/*
 * @fn GetStatus
 * @brief to get CasDecodeController instance status
 * @return EngineStat
 */
EngineStat CasDecodeController::GetStatus() const
{
    return m_engineStatus;
}

/*
 * @fn SetStatus
 * @brief to set CasDecodeController instance status
 * @param[in] stat CasDecodeController instance status, pass by value, of (type <tt>enum EngineStat</tt>)
 * @return void
 */
void CasDecodeController::SetStatus(const EngineStat stat)
{
    m_engineStatus = stat;
}

/*
 * @fn IsStatus
 * @brief to judge CasDecodeController instance status
 * @param[in] stat CasDecodeController instance status, pass by value, of (type <tt>enum EngineStat</tt>)
 * @return bool
 */
bool CasDecodeController::IsStatus(const EngineStat stat) const
{
    return m_engineStatus == stat;
}

/*
 * @fn GetStatics
 * @brief to get CasDecodeController instance statistics information
 * @return DecoderStatics
 */
DecoderStatistics CasDecodeController::GetStatistics() const
{
    DecoderStatistics decoderStatistics = { 0 };
    if (IsStatus(EngineStat::ENGINE_RUNNING)) {
        decoderStatistics.decodeFps = m_decoder->GetFps();
    }
    INFO("Decode Frame Rate: %u fps", decoderStatistics.decodeFps);
    return decoderStatistics;
}

void CasDecodeController::SetFirstFrameListener(CasFirstVideoFrameListener *listener)
{
    m_firstFrameListener = listener;
}

void CasDecodeController::SetDecodeStatListener(CasVideoDecodeStatListener *listener)
{
    m_decodeStatListener = listener;
}